#ifndef SKYROAD_ROLLING_FILE_H_
#define SKYROAD_ROLLING_FILE_H_

#if defined(_MSC_VER) 
#pragma warning ( disable : 4355)
#pragma warning(disable:4996)
#endif 

#include <fstream>
#include <string>
#include <sstream>
#include <boost/thread.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>

#include <skyroad/util/flags.hpp>

namespace fs = boost::filesystem;
namespace fio = boost::iostreams;

struct rolling_file_settings
{
    typedef flag<rolling_file_settings> flag;

    rolling_file_settings() 
        : max_size_bytes(this, 1024 * 1024)
        , file_count(this, 10)
        , initial_erase(this, false)
        , start_where_size_not_exceeded(this, true) 
        , flush_each_time(this, true)
        , extra_flags(this, std::ios_base::out) 
        , console_print(this, false)
    {}

	/// maximum size in bytes, by default 1Mb
	flag::t<int> max_size_bytes;
	/// how many files has a rolling file, by default, 10
	flag::t<int> file_count;
	/// if true, it initially erases all files from the rolling file (by default, false)
	flag::t<bool> initial_erase;
	/// if true, it starts with the first file that hasn't exceeded the max size;
	/// otherwise, it starts with the first file (default = true)
	flag::t<bool> start_where_size_not_exceeded;

	/// if true, always flush after write (by default, true)
	flag::t<bool> flush_each_time;

	/// just in case you have some extra flags to pass, when opening each file
	flag::t<std::ios_base::openmode> extra_flags;
	
	flag::t<bool> console_print;
};

class rolling_file 
{
public:
    rolling_file (const std::string& name_prefix, rolling_file_settings flags ) 
    // many thanks to Martin Bauer
    : m_directory(fs::path(name_prefix).branch_path().string()), m_name_prefix(name_prefix), m_flags(flags), m_cur_idx(0)
    {
            restart();
    }

    template<class msg_type> 
    rolling_file& operator << (const msg_type& msg) 
    {
        if (m_flags.console_print())
        {
            std::cout << msg;
        }

        boost::lock_guard<boost::mutex> lock(m_mutex);
        try 
        {
            create_if_needed();
            *m_out << msg;
            if ( m_flags.flush_each_time())
            {
                m_out->flush();
            }

            if ( m_out->tellp() > m_flags.max_size_bytes()) 
            {
                m_cur_idx = (m_cur_idx + 1) % m_flags.file_count();
                recreate_file();
            }
        }
        catch (...)
        {
            m_out.reset();
        }
        return *this;
    }

    void flush()
    {
        if ( m_out)
        {
            m_out->flush();
        }
    }

private:
    void restart()
    {
        if ( m_flags.initial_erase() )
        {
            for ( int idx = 0; idx < m_flags.file_count(); ++idx)
            {
                if ( fs::exists( file_name(idx) ))
                {
                    fs::remove( file_name(idx) );
                }
            }
        }

        // see what file to start from
        if ( m_flags.start_where_size_not_exceeded() )
        {
            for ( m_cur_idx = 0; m_cur_idx < m_flags.file_count(); ++m_cur_idx )
            {
                if ( fs::exists( file_name(m_cur_idx) )) 
                {
                    if ( static_cast<int>(fs::file_size( file_name(m_cur_idx))) < m_flags.max_size_bytes() )
                    {
                        // file hasn't reached max size
                        break;
                    }
                }
                else
                {
                    // file not found, we'll create it now
                    break;
                }

            }
            if ( m_cur_idx >= m_flags.file_count())
            {
              // all files are too full (we'll overwrite the first one)
             m_cur_idx = 0;
            }
        }

        // force reopen, even if already open
        m_out = boost::shared_ptr< fio::stream<fio::file_descriptor_sink> >();
      
    }

    std::string file_name(int idx)
    {
        std::ostringstream out; 
        if ( idx > 0)
        {
            out << m_name_prefix << "." << (idx+1);
        }
        else
        {
            out << m_name_prefix;
        }
        return out.str();
    }

    void recreate_file() 
    {
        fs::create_directories(m_directory);
        if (m_out) 
        {
            m_out->flush();
            m_out->close();
        }
        // many thanks to Benjamin de Dardel!
        m_out = boost::shared_ptr< fio::stream<fio::file_descriptor_sink> >(new fio::stream<fio::file_descriptor_sink>( file_name(m_cur_idx),
            m_flags.extra_flags() | std::ios_base::out | std::ios_base::app));

        if ( fs::file_size( file_name(m_cur_idx)) > m_flags.max_size_bytes())
        {
            // this file is already full - clear it first
            m_out = boost::shared_ptr< fio::stream<fio::file_descriptor_sink> >(new fio::stream<fio::file_descriptor_sink>( file_name(m_cur_idx).c_str(),
                m_flags.extra_flags() | std::ios_base::out | std::ios_base::trunc));
        }
    }

    void create_if_needed()
    {
        if ( !m_out)
            recreate_file();
    }

    boost::shared_ptr< fio::stream<fio::file_descriptor_sink> > m_out;
    std::string m_directory;
    std::string m_name_prefix;
    rolling_file_settings m_flags;
    int m_cur_idx;
    boost::mutex m_mutex;
};
#endif